home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / Introspector.java < prev    next >
Text File  |  1998-09-25  |  31KB  |  944 lines

  1. /*
  2.  * @(#)Introspector.java    1.73 98/07/08
  3.  *
  4.  * Copyright 1996-1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  * 
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.beans;
  16.  
  17. import java.lang.reflect.*;
  18.  
  19. /**
  20.  * The Introspector class provides a standard way for tools to learn about
  21.  * the properties, events, and methods supported by a target Java Bean.
  22.  * <p>
  23.  * For each of those three kinds of information, the Introspector will
  24.  * separately analyze the bean's class and superclasses looking for
  25.  * either explicit or implicit information and use that information to
  26.  * build a BeanInfo object that comprehensively describes the target bean.
  27.  * <p>
  28.  * For each class "Foo", explicit information may be available if there exists
  29.  * a corresponding "FooBeanInfo" class that provides a non-null value when
  30.  * queried for the information.   We first look for the BeanInfo class by
  31.  * taking the full package-qualified name of the target bean class and
  32.  * appending "BeanInfo" to form a new class name.  If this fails, then
  33.  * we take the final classname component of this name, and look for that
  34.  * class in each of the packages specified in the BeanInfo package search
  35.  * path.
  36.  * <p>
  37.  * Thus for a class such as "sun.xyz.OurButton" we would first look for a
  38.  * BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd
  39.  * look in each package in the BeanInfo search path for an OurButtonBeanInfo
  40.  * class.  With the default search path, this would mean looking for
  41.  * "sun.beans.infos.OurButtonBeanInfo".
  42.  * <p>
  43.  * If a class provides explicit BeanInfo about itself then we add that to
  44.  * the BeanInfo information we obtained from analyzing any derived classes,
  45.  * but we regard the explicit information as being definitive for the current
  46.  * class and its base classes, and do not proceed any further up the superclass
  47.  * chain.
  48.  * <p>
  49.  * If we don't find explicit BeanInfo on a class, we use low-level
  50.  * reflection to study the methods of the class and apply standard design
  51.  * patterns to identify property accessors, event sources, or public
  52.  * methods.  We then proceed to analyze the class's superclass and add
  53.  * in the information from it (and possibly on up the superclass chain).
  54.  */
  55.  
  56. public class Introspector {
  57.  
  58.     //======================================================================
  59.     //                 Public methods
  60.     //======================================================================
  61.  
  62.  
  63.     /**
  64.      * Introspect on a Java bean and learn about all its properties, exposed
  65.      * methods, and events.
  66.      *
  67.      * @param beanClass  The bean class to be analyzed.
  68.      * @return  A BeanInfo object describing the target bean.
  69.      * @exception IntrospectionException if an exception occurs during
  70.      *              introspection.
  71.      */
  72.     public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException {
  73.     BeanInfo bi = (BeanInfo)beanInfoCache.get(beanClass);
  74.     if (bi == null) {
  75.         bi = (new Introspector(beanClass, null)).getBeanInfo();
  76.         beanInfoCache.put(beanClass, bi);
  77.     }
  78.     return bi;
  79.     }
  80.  
  81.     /**
  82.      * Introspect on a Java bean and learn all about its properties, exposed
  83.      * methods, below a given "stop" point.
  84.      *
  85.      * @param bean The bean class to be analyzed.
  86.      * @param stopClass The baseclass at which to stop the analysis.  Any
  87.      *    methods/properties/events in the stopClass or in its baseclasses
  88.      *    will be ignored in the analysis.
  89.      * @exception IntrospectionException if an exception occurs during
  90.      *              introspection.
  91.      */
  92.     public static BeanInfo getBeanInfo(Class beanClass,    Class stopClass)
  93.                         throws IntrospectionException {
  94.     return (new Introspector(beanClass, stopClass)).getBeanInfo();
  95.     }
  96.  
  97.     /**
  98.      * Utility method to take a string and convert it to normal Java variable
  99.      * name capitalization.  This normally means converting the first
  100.      * character from upper case to lower case, but in the (unusual) special
  101.      * case when there is more than one character and both the first and
  102.      * second characters are upper case, we leave it alone.
  103.      * <p>
  104.      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
  105.      * as "URL".
  106.      *
  107.      * @param  name The string to be decapitalized.
  108.      * @return  The decapitalized version of the string.
  109.      */
  110.     public static String decapitalize(String name) {
  111.     if (name == null || name.length() == 0) {
  112.         return name;
  113.     }
  114.     if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
  115.             Character.isUpperCase(name.charAt(0))){
  116.         return name;
  117.     }
  118.     char chars[] = name.toCharArray();
  119.     chars[0] = Character.toLowerCase(chars[0]);
  120.     return new String(chars);
  121.     }
  122.  
  123.     /**
  124.      * @return  The array of package names that will be searched in
  125.      *        order to find BeanInfo classes.
  126.      * <p>     This is initially set to {"sun.beans.infos"}.
  127.      */
  128.  
  129.     public static String[] getBeanInfoSearchPath() {
  130.     return searchPath;
  131.     }
  132.  
  133.     /**
  134.      * Change the list of package names that will be used for
  135.      *        finding BeanInfo classes.
  136.      * @param path  Array of package names.
  137.      */
  138.  
  139.     public static void setBeanInfoSearchPath(String path[]) {
  140.     searchPath = path;
  141.     }
  142.  
  143.  
  144.     //======================================================================
  145.     //             Private implementation methods
  146.     //======================================================================
  147.  
  148.     private Introspector(Class beanClass, Class stopClass)
  149.                         throws IntrospectionException {
  150.     this.beanClass = beanClass;
  151.  
  152.     // Check stopClass is a superClass of startClass.
  153.     if (stopClass != null) {
  154.         boolean isSuper = false;
  155.         for (Class c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {
  156.             if (c == stopClass) {
  157.             isSuper = true;
  158.             }
  159.         }
  160.         if (!isSuper) {
  161.             throw new IntrospectionException(stopClass.getName() + " not superclass of " + 
  162.                     beanClass.getName());
  163.         }
  164.     }
  165.  
  166.     informant = findInformant(beanClass);
  167.  
  168.     if (beanClass.getSuperclass() != stopClass) {
  169.         if (stopClass == null) {
  170.             superBeanInfo = Introspector.getBeanInfo(
  171.                 beanClass.getSuperclass());
  172.         } else {
  173.             superBeanInfo = Introspector.getBeanInfo(
  174.                 beanClass.getSuperclass(), stopClass);
  175.         }
  176.     }
  177.     if (informant != null) {
  178.         additionalBeanInfo = informant.getAdditionalBeanInfo();
  179.     } 
  180.     if (additionalBeanInfo == null) {
  181.         additionalBeanInfo = new BeanInfo[0];
  182.     }
  183.     }
  184.  
  185.    
  186.     private BeanInfo getBeanInfo() throws IntrospectionException {
  187.  
  188.     // the evaluation order here is import, as we evaluate the
  189.     // event sets and locate PropertyChangeListeners before we
  190.     // look for properties.
  191.     BeanDescriptor bd = getTargetBeanDescriptor();
  192.     EventSetDescriptor esds[] = getTargetEventInfo();
  193.     int defaultEvent = getTargetDefaultEventIndex();
  194.     PropertyDescriptor pds[] = getTargetPropertyInfo();
  195.     int defaultProperty = getTargetDefaultPropertyIndex();
  196.     MethodDescriptor mds[] = getTargetMethodInfo();
  197.  
  198.         return new GenericBeanInfo(bd, esds, defaultEvent, pds,
  199.             defaultProperty, mds, informant);
  200.     
  201.     }
  202.  
  203.     private BeanInfo findInformant(Class beanClass) {
  204.     String name = beanClass.getName() + "BeanInfo";
  205.         try {
  206.         return (java.beans.BeanInfo)instantiate(beanClass, name);
  207.     } catch (Exception ex) {
  208.         // Just drop through
  209.         }
  210.     // Now try checking if the bean is its own BeanInfo.
  211.         try {
  212.         if (isSubclass(beanClass, java.beans.BeanInfo.class)) {
  213.             return (java.beans.BeanInfo)beanClass.newInstance();
  214.         }
  215.     } catch (Exception ex) {
  216.         // Just drop through
  217.         }
  218.     // Now try looking for <searchPath>.fooBeanInfo
  219.        while (name.indexOf('.') > 0) {
  220.         name = name.substring(name.indexOf('.')+1);
  221.     }
  222.     for (int i = 0; i < searchPath.length; i++) {
  223.         try {
  224.         String fullName = searchPath[i] + "." + name;
  225.             return (java.beans.BeanInfo)instantiate(beanClass, fullName);
  226.         } catch (Exception ex) {
  227.            // Silently ignore any errors.
  228.         }
  229.     }
  230.     return null;
  231.     }
  232.  
  233.     /**
  234.      * @return An array of PropertyDescriptors describing the editable
  235.      * properties supported by the target bean.
  236.      */
  237.  
  238.     private PropertyDescriptor[] getTargetPropertyInfo() throws IntrospectionException {
  239.  
  240.     // Check if the bean has its own BeanInfo that will provide
  241.     // explicit information.
  242.         PropertyDescriptor[] explicit = null;
  243.     if (informant != null) {
  244.         explicit = informant.getPropertyDescriptors();
  245.         int ix = informant.getDefaultPropertyIndex();
  246.         if (ix >= 0 && ix < explicit.length) {
  247.         defaultPropertyName = explicit[ix].getName();
  248.         }
  249.         }
  250.  
  251.     if (explicit == null && superBeanInfo != null) {
  252.         // We have no explicit BeanInfo properties.  Check with our parent.
  253.         PropertyDescriptor supers[] = superBeanInfo.getPropertyDescriptors();
  254.         for (int i = 0 ; i < supers.length; i++) {
  255.         addProperty(supers[i]);
  256.         }
  257.         int ix = superBeanInfo.getDefaultPropertyIndex();
  258.         if (ix >= 0 && ix < supers.length) {
  259.         defaultPropertyName = supers[ix].getName();
  260.         }
  261.     }
  262.  
  263.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  264.         PropertyDescriptor additional[] = additionalBeanInfo[i].getPropertyDescriptors();
  265.         if (additional != null) {
  266.             for (int j = 0 ; j < additional.length; j++) {
  267.             addProperty(additional[j]);
  268.             }
  269.         }
  270.     }
  271.  
  272.     if (explicit != null) {
  273.         // Add the explicit informant data to our results.
  274.         for (int i = 0 ; i < explicit.length; i++) {
  275.         addProperty(explicit[i]);
  276.         }
  277.  
  278.     } else {
  279.  
  280.         // Apply some reflection to the current class.
  281.  
  282.         // First get an array of all the beans methods at this level
  283.         Method methodList[] = getDeclaredMethods(beanClass);
  284.  
  285.         // Now analyze each method.
  286.         for (int i = 0; i < methodList.length; i++) {
  287.             Method method = methodList[i];
  288.             // skip static and non-public methods.
  289.         int mods = method.getModifiers();
  290.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  291.             continue;
  292.         }
  293.             String name = method.getName();
  294.             Class argTypes[] = method.getParameterTypes();
  295.             Class resultType = method.getReturnType();
  296.         int argCount = argTypes.length;
  297.         PropertyDescriptor pd = null;
  298.  
  299.         try {
  300.  
  301.                 if (argCount == 0) {
  302.                 if (name.startsWith("get")) {
  303.                     // Simple getter
  304.                         pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  305.                         method, null);
  306.                     } else if (resultType == boolean.class && name.startsWith("is")) {
  307.                     // Boolean getter
  308.                         pd = new PropertyDescriptor(decapitalize(name.substring(2)),
  309.                         method, null);
  310.                 }
  311.                 } else if (argCount == 1) {
  312.                 if (argTypes[0] == int.class && name.startsWith("get")) {
  313.                     pd = new IndexedPropertyDescriptor(
  314.                     decapitalize(name.substring(3)),
  315.                     null, null,
  316.                     method,    null);
  317.                 } else if (resultType == void.class && name.startsWith("set")) {
  318.                     // Simple setter
  319.                         pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  320.                         null, method);
  321.                     if (throwsException(method, PropertyVetoException.class)) {
  322.                     pd.setConstrained(true);
  323.                 }            
  324.                 }
  325.                 } else if (argCount == 2) {
  326.                 if (argTypes[0] == int.class && name.startsWith("set")) {
  327.                         pd = new IndexedPropertyDescriptor(
  328.                         decapitalize(name.substring(3)),
  329.                         null, null,
  330.                         null, method);
  331.                     if (throwsException(method, PropertyVetoException.class)) {
  332.                     pd.setConstrained(true);            
  333.                 }
  334.             }
  335.             }
  336.         } catch (IntrospectionException ex) {
  337.             // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
  338.                 // constructor fins that the method violates details of the deisgn
  339.             // pattern, e.g. by having an empty name, or a getter returning
  340.             // void , or whatever.
  341.             pd = null;
  342.         }
  343.  
  344.         if (pd != null) {
  345.             // If this class or one of its base classes is a PropertyChange
  346.             // source, then we assume that any properties we discover are "bound".
  347.             if (propertyChangeSource) {
  348.             pd.setBound(true);
  349.             }
  350.             addProperty(pd);
  351.         }
  352.         }
  353.     }
  354.  
  355.     // Allocate and populate the result array.
  356.     PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
  357.     java.util.Enumeration elements = properties.elements();
  358.     for (int i = 0; i < result.length; i++) {
  359.         result[i] = (PropertyDescriptor)elements.nextElement();
  360.         if (defaultPropertyName != null
  361.              && defaultPropertyName.equals(result[i].getName())) {
  362.         defaultPropertyIndex = i;
  363.         }
  364.     }
  365.  
  366.     return result;
  367.     }
  368.  
  369.     void addProperty(PropertyDescriptor pd) {
  370.     String name = pd.getName();
  371.     PropertyDescriptor old = (PropertyDescriptor)properties.get(name);
  372.     if (old == null) {
  373.         properties.put(name, pd);
  374.         return;
  375.     }
  376.     // If the property type has changed, use the new descriptor.
  377.     Class opd = old.getPropertyType();
  378.     Class npd = pd.getPropertyType();
  379.     if (opd != null && npd != null && opd != npd) {
  380.         properties.put(name, pd);
  381.         return;
  382.     }
  383.  
  384.     PropertyDescriptor composite;
  385.     if (old instanceof IndexedPropertyDescriptor ||
  386.                 pd instanceof IndexedPropertyDescriptor) {
  387.         composite = new IndexedPropertyDescriptor(old, pd);
  388.     } else {
  389.         composite = new PropertyDescriptor(old, pd);
  390.     }
  391.     properties.put(name, composite);
  392.     }
  393.  
  394.  
  395.     /**
  396.      * @return An array of EventSetDescriptors describing the kinds of 
  397.      * events fired by the target bean.
  398.      */
  399.     private EventSetDescriptor[] getTargetEventInfo() throws IntrospectionException {
  400.  
  401.     // Check if the bean has its own BeanInfo that will provide
  402.     // explicit information.
  403.         EventSetDescriptor[] explicit = null;
  404.     if (informant != null) {
  405.         explicit = informant.getEventSetDescriptors();
  406.         int ix = informant.getDefaultEventIndex();
  407.         if (ix >= 0 && ix < explicit.length) {
  408.         defaultEventName = explicit[ix].getName();
  409.         }
  410.     }
  411.  
  412.     if (explicit == null && superBeanInfo != null) {
  413.         // We have no explicit BeanInfo events.  Check with our parent.
  414.         EventSetDescriptor supers[] = superBeanInfo.getEventSetDescriptors();
  415.         for (int i = 0 ; i < supers.length; i++) {
  416.         addEvent(supers[i]);
  417.         }
  418.         int ix = superBeanInfo.getDefaultEventIndex();
  419.         if (ix >= 0 && ix < supers.length) {
  420.         defaultEventName = supers[ix].getName();
  421.         }
  422.     }
  423.  
  424.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  425.         EventSetDescriptor additional[] = additionalBeanInfo[i].getEventSetDescriptors();
  426.         if (additional != null) {
  427.             for (int j = 0 ; j < additional.length; j++) {
  428.             addEvent(additional[j]);
  429.             }
  430.         }
  431.     }
  432.  
  433.     if (explicit != null) {
  434.         // Add the explicit informant data to our results.
  435.         for (int i = 0 ; i < explicit.length; i++) {
  436.         addEvent(explicit[i]);
  437.         }
  438.  
  439.     } else {
  440.  
  441.         // Apply some reflection to the current class.
  442.  
  443.         // Get an array of all the beans methods at this level
  444.         Method methodList[] = getDeclaredMethods(beanClass);
  445.  
  446.         // Find all suitable "add" and "remove" methods.
  447.         java.util.Hashtable adds = new java.util.Hashtable();
  448.         java.util.Hashtable removes = new java.util.Hashtable();
  449.         for (int i = 0; i < methodList.length; i++) {
  450.             Method method = methodList[i];
  451.             // skip static and non-public methods.
  452.         int mods = method.getModifiers();
  453.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  454.             continue;
  455.         }
  456.             String name = method.getName();
  457.  
  458.             Class argTypes[] = method.getParameterTypes();
  459.             Class resultType = method.getReturnType();
  460.  
  461.             if (name.startsWith("add") && argTypes.length == 1 &&
  462.                     resultType == Void.TYPE) {
  463.             String compound = name.substring(3) + ":" + argTypes[0];
  464.             adds.put(compound, method);
  465.             } else if (name.startsWith("remove") && argTypes.length == 1 &&
  466.                     resultType == Void.TYPE) {
  467.             String compound = name.substring(6) + ":" + argTypes[0];
  468.             removes.put(compound, method);
  469.             }
  470.         }
  471.  
  472.            // Now look for matching addFooListener+removeFooListener pairs.
  473.           java.util.Enumeration keys = adds.keys();
  474.         String beanClassName = beanClass.getName();
  475.         while (keys.hasMoreElements()) {
  476.             String compound = (String) keys.nextElement();
  477.             // Skip any "add" which doesn't have a matching "remove".
  478.             if (removes.get(compound) == null) {
  479.             continue;
  480.             } 
  481.             // Method name has to end in "Listener"
  482.             if (compound.indexOf("Listener:") <= 0) {
  483.             continue;
  484.             }
  485.  
  486.             String listenerName = compound.substring(0, compound.indexOf(':'));
  487.             String eventName = decapitalize(listenerName.substring(0, listenerName.length()-8));
  488.             Method addMethod = (Method)adds.get(compound);
  489.             Method removeMethod = (Method)removes.get(compound);
  490.             Class argType = addMethod.getParameterTypes()[0];
  491.  
  492.             // Check if the argument type is a subtype of EventListener
  493.             if (!Introspector.isSubclass(argType, eventListenerType)) {
  494.                 continue;
  495.             }
  496.  
  497.                 // generate a list of Method objects for each of the target methods:
  498.             Method allMethods[] = argType.getMethods();
  499.             int count = 0;
  500.             for (int i = 0; i < allMethods.length; i++) {
  501.                 if (isEventHandler(allMethods[i])) {
  502.                 count++;
  503.                 } else {
  504.                 allMethods[i] = null;
  505.                 }
  506.             }
  507.             Method methods[] = new Method[count];
  508.             int j = 0;
  509.             for (int i = 0; i < allMethods.length; i++) {
  510.                 if (allMethods[i] != null) {
  511.                 methods[j++] = allMethods[i];
  512.                 }
  513.              }
  514.  
  515.               EventSetDescriptor esd = new EventSetDescriptor(eventName, argType,
  516.                         methods, addMethod, removeMethod);
  517.  
  518.         // If the adder method throws the TooManyListenersException then it
  519.         // is a Unicast event source.
  520.         if (throwsException(addMethod,
  521.             java.util.TooManyListenersException.class)) {
  522.             esd.setUnicast(true);
  523.         }
  524.  
  525.         addEvent(esd);
  526.         }
  527.     }
  528.  
  529.     // Allocate and populate the result array.
  530.     EventSetDescriptor result[] = new EventSetDescriptor[events.size()];
  531.     java.util.Enumeration elements = events.elements();
  532.     for (int i = 0; i < result.length; i++) {
  533.         result[i] = (EventSetDescriptor)elements.nextElement();
  534.         if (defaultEventName != null 
  535.                 && defaultEventName.equals(result[i].getName())) {
  536.         defaultEventIndex = i;
  537.         }
  538.     }
  539.  
  540.     return result;
  541.     }
  542.  
  543.     void addEvent(EventSetDescriptor esd) {
  544.     String key = esd.getName();
  545.     if (key.equals("propertyChange")) {
  546.         propertyChangeSource = true;
  547.     }
  548. // Symantec hack
  549. //__SYMC__
  550.         if (esd.getListenerType().isAssignableFrom(java.beans.VetoableChangeListener.class))
  551.             key += "Vetoable";
  552.         else if (esd.getListenerType().isAssignableFrom(java.beans.PropertyChangeListener.class))
  553.             key += "Property";
  554. //__SYMC__
  555.     EventSetDescriptor old = (EventSetDescriptor)events.get(key);
  556.     if (old == null) {
  557.         events.put(key, esd);
  558.         return;
  559.     }
  560.     EventSetDescriptor composite = new EventSetDescriptor(old, esd);
  561.     events.put(key, composite);
  562.     }
  563.  
  564.     /**
  565.      * @return An array of MethodDescriptors describing the private
  566.      * methods supported by the target bean.
  567.      */
  568.     private MethodDescriptor[] getTargetMethodInfo() throws IntrospectionException {
  569.  
  570.     // Check if the bean has its own BeanInfo that will provide
  571.     // explicit information.
  572.         MethodDescriptor[] explicit = null;
  573.     if (informant != null) {
  574.         explicit = informant.getMethodDescriptors();
  575.     }
  576.  
  577.     if (explicit == null && superBeanInfo != null) {
  578.         // We have no explicit BeanInfo methods.  Check with our parent.
  579.         MethodDescriptor supers[] = superBeanInfo.getMethodDescriptors();
  580.         for (int i = 0 ; i < supers.length; i++) {
  581.         addMethod(supers[i]);
  582.         }
  583.     }
  584.  
  585.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  586.         MethodDescriptor additional[] = additionalBeanInfo[i].getMethodDescriptors();
  587.         if (additional != null) {
  588.             for (int j = 0 ; j < additional.length; j++) {
  589.             addMethod(additional[j]);
  590.             }
  591.         }
  592.     }
  593.  
  594.     if (explicit != null) {
  595.         // Add the explicit informant data to our results.
  596.         for (int i = 0 ; i < explicit.length; i++) {
  597.         addMethod(explicit[i]);
  598.         }
  599.  
  600.     } else {
  601.  
  602.         // Apply some reflection to the current class.
  603.  
  604.         // First get an array of all the beans methods at this level
  605.         Method methodList[] = getDeclaredMethods(beanClass);
  606.  
  607.         // Now analyze each method.
  608.         for (int i = 0; i < methodList.length; i++) {
  609.             Method method = methodList[i];
  610.             // skip non-public methods.
  611.         if (!Modifier.isPublic(method.getModifiers())) {
  612.             continue;
  613.         }
  614.         MethodDescriptor md = new MethodDescriptor(method);
  615.         addMethod(md);
  616.         }
  617.     }
  618.  
  619.     // Allocate and populate the result array.
  620.     MethodDescriptor result[] = new MethodDescriptor[methods.size()];
  621.     java.util.Enumeration elements = methods.elements();
  622.     for (int i = 0; i < result.length; i++) {
  623.         result[i] = (MethodDescriptor)elements.nextElement();
  624.     }
  625.  
  626.     return result;
  627.     }
  628.  
  629.     private void addMethod(MethodDescriptor md) {
  630.     // We have to be careful here to distinguish method by both name
  631.     // and argument lists.
  632.     // This method gets called a *lot, so we try to be efficient.
  633.  
  634.     String name = md.getMethod().getName();
  635.  
  636.     MethodDescriptor old = (MethodDescriptor)methods.get(name);
  637.     if (old == null) {
  638.         // This is the common case.
  639.         methods.put(name, md);
  640.         return;
  641.     }    
  642.  
  643.     // We have a collision on method names.  This is rare.
  644.  
  645.     // Check if old and md have the same type.
  646.     Class p1[] = md.getMethod().getParameterTypes();    
  647.     Class p2[] = old.getMethod().getParameterTypes();    
  648.     boolean match = false;
  649.     if (p1.length == p2.length) {
  650.         match = true;
  651.         for (int i = 0; i < p1.length; i++) {
  652.         if (p1[i] != p2[i]) {
  653.             match = false;
  654.             break;
  655.         }
  656.         }
  657.     }
  658.     if (match) {
  659.         MethodDescriptor composite = new MethodDescriptor(old, md);
  660.         methods.put(name, composite);
  661.         return;
  662.     }
  663.  
  664.     // We have a collision on method names with different type signatures.
  665.     // This is very rare.
  666.  
  667.     String longKey = makeQualifiedMethodName(md);
  668.     old = (MethodDescriptor)methods.get(longKey);
  669.     if (old == null) {
  670.         methods.put(longKey, md);
  671.         return;
  672.     }    
  673.     MethodDescriptor composite = new MethodDescriptor(old, md);
  674.     methods.put(longKey, composite);
  675.     }
  676.  
  677.     private String makeQualifiedMethodName(MethodDescriptor md) {
  678.     Method m = md.getMethod();
  679.     StringBuffer sb = new StringBuffer();
  680.     sb.append(m.getName());
  681.     sb.append("=");
  682.     Class params[] = m.getParameterTypes();
  683.     for (int i = 0; i < params.length; i++) {
  684.         sb.append(":");
  685.         sb.append(params[i].getName());
  686.     }
  687.     return sb.toString();
  688.     }
  689.  
  690.     private int getTargetDefaultEventIndex() {
  691.     return defaultEventIndex;
  692.     }
  693.  
  694.     private int getTargetDefaultPropertyIndex() {
  695.     return defaultPropertyIndex;
  696.     }
  697.  
  698.     private BeanDescriptor getTargetBeanDescriptor() throws IntrospectionException {
  699.     // Use explicit info, if available,
  700.     if (informant != null) {
  701.         BeanDescriptor bd = informant.getBeanDescriptor();
  702.         if (bd != null) {
  703.         return (bd);
  704.         }
  705.     }
  706.     // OK, fabricate a default BeanDescriptor.
  707.     return (new BeanDescriptor(beanClass));
  708.     }
  709.  
  710.     private boolean isEventHandler(Method m) throws IntrospectionException {
  711.     // Right now we assume that a method is an event handler if it
  712.     // has a single argument, whose type name includes the word
  713.     // "Event".  The real answer is that the argument type should
  714.     // inherit from java.util.Event or somesuch, but we're not quite
  715.     // there yet.
  716.     try {
  717.         Class argTypes[] = m.getParameterTypes();
  718.         if (argTypes.length != 1) {
  719.         return (false);
  720.         }
  721.         String type = "" + argTypes[0];
  722.         if (type.indexOf("Event") >= 0) {
  723.         return (true);
  724.         } else {
  725.         return (false);
  726.         }
  727.         
  728.     } catch (Exception ex) {
  729.         throw new IntrospectionException("Unexpected reflection exception: " + ex);
  730.     }
  731.     }
  732.  
  733.     private static synchronized Method[] getDeclaredMethods(Class clz) {
  734.     // Looking up Class.getDeclaredMethods is realtively expensive,
  735.     // so we cache the results.
  736.     if (declaredMethodCache == null) {
  737.        declaredMethodCache = new java.util.Hashtable();
  738.     }
  739.     Method[] result = (Method[])declaredMethodCache.get(clz);
  740.     if (result == null) {
  741.         result = clz.getDeclaredMethods();
  742.         declaredMethodCache.put(clz, result);
  743.     }
  744.     return result;
  745.     }
  746.  
  747.     //======================================================================
  748.     // Package private support methods.
  749.     //======================================================================
  750.  
  751.     /**
  752.      * Find a target methodName on a given class.
  753.      */
  754.  
  755.     static Method findMethod(Class cls, String methodName, int argCount) 
  756.             throws IntrospectionException {
  757.     if (methodName == null) {
  758.         return null;
  759.     }
  760.  
  761.     // For overriden methods we need to find the most derived version.
  762.     // So we start with the given cls and walk up the superclass chain.
  763.     while (cls != null) {
  764.             Method methods[] = getDeclaredMethods(cls);
  765.         for (int i = 0; i < methods.length; i++) {
  766.             Method method = methods[i];
  767.             // skip static and non-public methods.
  768.         int mods = method.getModifiers();
  769.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  770.             continue;
  771.         }
  772.             if (method.getName().equals(methodName) &&
  773.             method.getParameterTypes().length == argCount) {
  774.                 return method;
  775.              }
  776.         }
  777.         cls = cls.getSuperclass();
  778.     }
  779.  
  780.     // We failed to find a suitable method
  781.     throw new IntrospectionException("No method \"" + methodName + 
  782.                     "\" with " + argCount + " arg(s)");
  783.     }
  784.  
  785.     /**
  786.      * Return true if class a is either equivalent to class b, or
  787.      * if class a is a subclass of class b.
  788.      * Note tht either or both "Class" objects may represent interfaces.
  789.      */
  790.     static  boolean isSubclass(Class a, Class b) {
  791.     // We rely on the fact that for any given java class or
  792.         // primtitive type there is a unqiue Class object, so
  793.     // we can use object equivalence in the comparisons.
  794.     if (a == b) {
  795.         return true;
  796.     }
  797.     if (a == null || b == null) {
  798.         return false;
  799.     }
  800.     for (Class x = a; x != null; x = x.getSuperclass()) {
  801.         if (x == b) {    
  802.         return true;
  803.         }
  804.         if (b.isInterface()) {
  805.         Class interfaces[] = x.getInterfaces();
  806.         for (int i = 0; i < interfaces.length; i++) {
  807.             if (isSubclass(interfaces[i], b)) {
  808.             return true;
  809.             }
  810.         }
  811.         }
  812.     }
  813.     return false;
  814.     }
  815.  
  816.     /**
  817.      * Return true iff the given method throws the given exception.
  818.      */
  819.     private boolean throwsException(Method method, Class exception) {
  820.     Class exs[] = method.getExceptionTypes();
  821.     for (int i = 0; i < exs.length; i++) {
  822.         if (exs[i] == exception) {
  823.         return true;
  824.         }
  825.     }
  826.     return false;
  827.     }
  828.  
  829.     /**
  830.      * Try to create an instance of a named class.
  831.      * First try the classloader of "sibling", then try the system
  832.      * classloader.
  833.      */
  834.  
  835.     static Object instantiate(Class sibling, String className)
  836.          throws InstantiationException, IllegalAccessException,
  837.                         ClassNotFoundException {
  838.     // First check with sibling's classloader (if any). 
  839.     ClassLoader cl = sibling.getClassLoader();
  840.     if (cl != null) {
  841.         try {
  842.             Class cls = cl.loadClass(className);
  843.         return cls.newInstance();
  844.         } catch (Exception ex) {
  845.             // Just drop through and try the system classloader.
  846.         }
  847.         }
  848.     // Now try the system classloader.
  849.     Class cls = Class.forName(className);
  850.     return cls.newInstance();
  851.     }
  852.  
  853.     //======================================================================
  854.  
  855.     private BeanInfo informant;
  856.     private boolean propertyChangeSource = false;
  857.     private Class beanClass;
  858.     private BeanInfo superBeanInfo;
  859.     private BeanInfo additionalBeanInfo[];
  860.     private static java.util.Hashtable beanInfoCache = new java.util.Hashtable();
  861.     private static Class eventListenerType = java.util.EventListener.class;
  862.     private String defaultEventName;
  863.     private String defaultPropertyName;
  864.     private int defaultEventIndex = -1;
  865.     private int defaultPropertyIndex = -1;
  866.  
  867.     // Methods maps from Method objects to MethodDescriptors
  868.     private java.util.Hashtable methods = new java.util.Hashtable();
  869.  
  870.     // Cache of Class.getDeclaredMethods:
  871.     private static java.util.Hashtable declaredMethodCache;
  872.  
  873.     // properties maps from String names to PropertyDescriptors
  874.     private java.util.Hashtable properties = new java.util.Hashtable();
  875.  
  876.     // events maps from String names to EventSetDescriptors
  877.     private java.util.Hashtable events = new java.util.Hashtable();
  878.  
  879.     private static String[] searchPath = { "sun.beans.infos" };
  880.  
  881. }
  882.  
  883. //===========================================================================
  884.  
  885. /**
  886.  * Package private implementation support class for Introspector's
  887.  * internal use.
  888.  */
  889.  
  890. class GenericBeanInfo extends SimpleBeanInfo {
  891.  
  892.     public GenericBeanInfo(BeanDescriptor beanDescriptor,
  893.         EventSetDescriptor[] events, int defaultEvent,
  894.         PropertyDescriptor[] properties, int defaultProperty,
  895.         MethodDescriptor[] methods, BeanInfo targetBeanInfo) {
  896.     this.beanDescriptor = beanDescriptor;
  897.     this.events = events;
  898.     this.defaultEvent = defaultEvent;
  899.     this.properties = properties;
  900.     this.defaultProperty = defaultProperty;
  901.     this.methods = methods;
  902.     this.targetBeanInfo = targetBeanInfo;
  903.     }
  904.  
  905.     public PropertyDescriptor[] getPropertyDescriptors() {
  906.     return properties;
  907.     }
  908.  
  909.     public int getDefaultPropertyIndex() {
  910.     return defaultProperty;
  911.     }
  912.  
  913.     public EventSetDescriptor[] getEventSetDescriptors() {
  914.     return events;
  915.     }
  916.  
  917.     public int getDefaultEventIndex() {
  918.     return defaultEvent;
  919.     }
  920.  
  921.     public MethodDescriptor[] getMethodDescriptors() {
  922.     return methods;
  923.     }
  924.  
  925.     public BeanDescriptor getBeanDescriptor() {
  926.     return beanDescriptor;
  927.     }
  928.  
  929.     public java.awt.Image getIcon(int iconKind) {
  930.     if (targetBeanInfo != null) {
  931.         return targetBeanInfo.getIcon(iconKind);
  932.     }
  933.     return super.getIcon(iconKind);
  934.     }
  935.  
  936.     private BeanDescriptor beanDescriptor;
  937.     private EventSetDescriptor[] events;
  938.     private int defaultEvent;
  939.     private PropertyDescriptor[] properties;
  940.     private int defaultProperty;
  941.     private MethodDescriptor[] methods;
  942.     private BeanInfo targetBeanInfo;
  943. }
  944.